///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_GEOMETRY_CSIZE_H_
#define _BUOLA_GEOMETRY_CSIZE_H_

#include <buola/utility/cpoint.h>

namespace buola {

///\addtogroup geometry
///@{

/////////////////////////////////////////////////////////////////////////////

/// 2D size

/// the main use of this class is to specify the size of a window or a
/// rectangle
///
/////////////////////////////////////////////////////////////////////////////

template<typename tType>
class CSize
{
    typedef std::numeric_limits<tType> TLimits;
    
public:
    typedef tType TType;
    typedef typename std::conditional<TLimits::is_integer,double,tType>::type TReal;
    
///\name constructor and destructor
//@{
    ///default constructor (leaves uninitialized)
    CSize()=default;

    ///copy constructor
    CSize(const CSize&)=default;

    ///from size of other type
    template<typename tO,OEnableIf<detail::QAutomaticPointConversion<tO,tType>> = 0>
    CSize(const CSize<tO> &pO)
        :   x(undefined(pO.x)?undefined<tType>():(tType)pO.x)
        ,   y(undefined(pO.y)?undefined<tType>():(tType)pO.y)
    {}

    template<typename tO,OEnableIfNot<detail::QAutomaticPointConversion<tO,tType>> = 0>
    explicit CSize(const CSize<tO> &pO)
        :   x(undefined(pO.x)?undefined<tType>():(tType)pO.x)
        ,   y(undefined(pO.y)?undefined<tType>():(tType)pO.y)
    {}

    ///from a scalar (sets width and height to the same value)
    explicit CSize(tType ps)
        :   x(ps)
        ,   y(ps)
    {}

    ///from width and height
    CSize(tType px,tType py)
        :   x(px)
        ,   y(py)
    {}

    ///undefined constructor
    CSize(NUndefined)
        :   x(undefined<tType>())
        ,   y(undefined<tType>())
    {}

    ///zero constructor
    CSize(NZero)
        :   x(0)
        ,   y(0)
    {}
//@}

    ///makes sure that the size is at least \p pMin
    void ConstrainMin(const CSize &pMin)
    {
        if(pMin.x>x) x=pMin.x;
        if(pMin.y>y) y=pMin.y;
    }

    ///makes sure that the size is at most \p pMax
    void ConstrainMax(const CSize &pMax)
    {
        if(pMax.x<x) x=pMax.x;
        if(pMax.y<y) y=pMax.y;
    }

    ///\brief get the size that best fits the given size, maintaining the
    ///aspect ratio of the size of this object
    CSize GetBestFit(const CSize &pFit) const
    {
        CSize lSize=pFit;

        TReal lRatio=(TReal)x/(TReal)y;
        TReal lFitRatio=TReal(lSize.x)/TReal(lSize.y);

        if(lFitRatio>lRatio)
        {
            lSize.x=tType(lSize.y*lRatio);
        }
        else if(lFitRatio<lRatio)
        {
            lSize.y=tType(lSize.x/lRatio);
        }
        return lSize;
    }

    ///subtracts one size from another (in-place version)
    CSize &operator-=(const CSize &pRH)
    {
        x-=pRH.x;
        y-=pRH.y;
        return *this;
    }

    ///adds one size to another (in-place version)
    CSize &operator+=(const CSize &pRH)
    {
        x+=pRH.x;
        y+=pRH.y;
        return *this;
    }

    ///comparison operator
    bool operator==(const CSize &pRH) const
    {
        return (x==pRH.x&&y==pRH.y);
    }

    ///inequality operator
    bool operator!=(const CSize &pRH) const
    {
        return (x!=pRH.x||y!=pRH.y);
    }

    bool operator<(const CSize &pRH) const
    {
        return (x<pRH.x&&y<pRH.y);
    }

    bool operator>(const CSize &pRH) const
    {
        return (x>pRH.x&&y>pRH.y);
    }

    bool operator<=(const CSize &pRH) const
    {
        return (x<=pRH.x&&y<=pRH.y);
    }

    bool operator>=(const CSize &pRH) const
    {
        return (x>=pRH.x&&y>=pRH.y);
    }

    ///returns true if the size is undefined
    bool Undefined() const
    {
        return (undefined(x)||undefined(y));
    }

    tType x;   ///< width
    tType y;   ///< height
};

///\relates CSize
///\brief negates the size
template<typename tType>
inline CSize<tType> operator-(const CSize<tType> &pSize)
{
    return CSize<tType>(-pSize.x,-pSize.y);
}

///\relates CPoint
///\relates CSize
///\brief subtracts a size from a point (in-place version)
template<typename tType>
CPoint<tType> &operator-=(CPoint<tType> &pLH,const CSize<tType> &pRH)
{
    pLH.x-=pRH.x;
    pLH.y-=pRH.y;
    return pLH;
}

///\relates CPoint
///\relates CSize
///\brief adds a size to a point (in-place version)
template<typename tType>
CPoint<tType> &operator+=(CPoint<tType> &pLH,const CSize<tType> &pRH)
{
    pLH.x+=pRH.x;
    pLH.y+=pRH.y;
    return pLH;
}

///\relates CPoint
///\relates CSize
///\brief adds a size to a point
template<typename tType>
inline CPoint<tType> operator+(const CPoint<tType> &pLH,const CSize<tType> &pRH)
{
    return CPoint<tType>(pLH.x+pRH.x,pLH.y+pRH.y);
}

///\relates CPoint
///\relates CSize
///\brief subtracts a size from a point
template<typename tType>
inline CPoint<tType> operator-(const CPoint<tType> &pLH,const CSize<tType> &pRH)
{
    return CPoint<tType>(pLH.x-pRH.x,pLH.y-pRH.y);
}

///\relates CSize
///\brief subtracts one size from another
template<typename tType>
inline CSize<tType> operator-(const CSize<tType> &pLH,const CSize<tType> &pRH)
{
    return CSize<tType>(pLH.x-pRH.x,pLH.y-pRH.y);
}

///\relates CSize
///\brief adds one size to another
template<typename tType>
inline CSize<tType> operator+(const CSize<tType> &pLH,const CSize<tType> &pRH)
{
    return CSize<tType>(pLH.x+pRH.x,pLH.y+pRH.y);
}

///\relates CSize
///\brief subtracts a scalar from both the width and height
template<typename tType>
inline CSize<tType> operator-(const CSize<tType> &pLH,double pRH)
{
    return CSize<tType>(pLH.x-pRH,pLH.y-pRH);
}

///\relates CSize
///\brief adds a scalar to both the width and height
template<typename tType>
inline CSize<tType> operator+(const CSize<tType> &pLH,double pRH)
{
    return CSize<tType>(pLH.x+pRH,pLH.y+pRH);
}

///\relates CSize
///\brief multiplies the size by a scalar
template<typename tType>
inline CSize<tType> operator*(const CSize<tType> &pLH,double pRH)
{
    return CSize<tType>(pLH.x*pRH,pLH.y*pRH);
}

///\relates CSize
///\brief divides the size by a scalar
template<typename tType>
inline CSize<tType> operator/(const CSize<tType> &pLH,double pRH)
{
    return CSize<tType>(pLH.x/pRH,pLH.y/pRH);
}

///\relates CPoint
///\relates CSize
///returns the point \p pP adjusted to a grid of size \p pS
template<typename tType>
CPoint<tType> to_grid(const CPoint<tType> &pP,const CSize<tType> &pS)
{
    typedef typename CSize<tType>::TReal TReal;
    return {tType(round(TReal(pP.x)/TReal(pS.x))*TReal(pS.x)),tType(round(TReal(pP.y)/TReal(pS.y))*TReal(pS.y))};
}

///\relates CSize
///returns the largest size with integer coordinates that is smaller than the given one
template<typename tType>
inline CSize<tType> floor(const CSize<tType> &pR)
{
    return {floor(pR.x),floor(pR.y)};
}

///\relates CSize
///returns the smallest size with integer coordinates that is larger than the given one
template<typename tType>
inline CSize<tType> ceil(const CSize<tType> &pR)
{
    return {ceil(pR.x),ceil(pR.y)};
}

///\relates CSize
///\brief output operator
template<typename tType>
inline io::CTextWriter &operator<<(io::CTextWriter &pWriter,const CSize<tType> &pSize)
{
    pWriter << '(' << pSize.x << 'x' << pSize.y <<')';
    return pWriter;
}

typedef CSize<double> CSize_d;
typedef CSize<float> CSize_f;
typedef CSize<int> CSize_i;

///@}

/*namespace buola*/ }

#endif
